
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import { BoundingRect, OrientedBoundingRect } from '../util/graphic.js';
export function prepareLayoutList(input) {
	var list = [];

	for (var i = 0; i < input.length; i++) {
		var rawItem = input[i];

		if (rawItem.defaultAttr.ignore) {
			continue;
		}

		var label = rawItem.label;
		var transform = label.getComputedTransform(); // NOTE: Get bounding rect after getComputedTransform, or label may not been updated by the host el.

		var localRect = label.getBoundingRect();
		var isAxisAligned = !transform || transform[1] < 1e-5 && transform[2] < 1e-5;
		var minMargin = label.style.margin || 0;
		var globalRect = localRect.clone();
		globalRect.applyTransform(transform);
		globalRect.x -= minMargin / 2;
		globalRect.y -= minMargin / 2;
		globalRect.width += minMargin;
		globalRect.height += minMargin;
		var obb = isAxisAligned ? new OrientedBoundingRect(localRect, transform) : null;
		list.push({
			label: label,
			labelLine: rawItem.labelLine,
			rect: globalRect,
			localRect: localRect,
			obb: obb,
			priority: rawItem.priority,
			defaultAttr: rawItem.defaultAttr,
			layoutOption: rawItem.computedLayoutOption,
			axisAligned: isAxisAligned,
			transform: transform
		});
	}

	return list;
}

function shiftLayout(list, xyDim, sizeDim, minBound, maxBound, balanceShift) {
	var len = list.length;

	if (len < 2) {
		return;
	}

	list.sort(function (a, b) {
		return a.rect[xyDim] - b.rect[xyDim];
	});
	var lastPos = 0;
	var delta;
	var adjusted = false;
	var shifts = [];
	var totalShifts = 0;

	for (var i = 0; i < len; i++) {
		var item = list[i];
		var rect = item.rect;
		delta = rect[xyDim] - lastPos;

		if (delta < 0) {
			// shiftForward(i, len, -delta);
			rect[xyDim] -= delta;
			item.label[xyDim] -= delta;
			adjusted = true;
		}

		var shift = Math.max(-delta, 0);
		shifts.push(shift);
		totalShifts += shift;
		lastPos = rect[xyDim] + rect[sizeDim];
	}

	if (totalShifts > 0 && balanceShift) {
		// Shift back to make the distribution more equally.
		shiftList(-totalShifts / len, 0, len);
	} // TODO bleedMargin?

	var first = list[0];
	var last = list[len - 1];
	var minGap;
	var maxGap;
	updateMinMaxGap(); // If ends exceed two bounds, squeeze at most 80%, then take the gap of two bounds.

	minGap < 0 && squeezeGaps(-minGap, 0.8);
	maxGap < 0 && squeezeGaps(maxGap, 0.8);
	updateMinMaxGap();
	takeBoundsGap(minGap, maxGap, 1);
	takeBoundsGap(maxGap, minGap, -1); // Handle bailout when there is not enough space.

	updateMinMaxGap();

	if (minGap < 0) {
		squeezeWhenBailout(-minGap);
	}

	if (maxGap < 0) {
		squeezeWhenBailout(maxGap);
	}

	function updateMinMaxGap() {
		minGap = first.rect[xyDim] - minBound;
		maxGap = maxBound - last.rect[xyDim] - last.rect[sizeDim];
	}

	function takeBoundsGap(gapThisBound, gapOtherBound, moveDir) {
		if (gapThisBound < 0) {
			// Move from other gap if can.
			var moveFromMaxGap = Math.min(gapOtherBound, -gapThisBound);

			if (moveFromMaxGap > 0) {
				shiftList(moveFromMaxGap * moveDir, 0, len);
				var remained = moveFromMaxGap + gapThisBound;

				if (remained < 0) {
					squeezeGaps(-remained * moveDir, 1);
				}
			} else {
				squeezeGaps(-gapThisBound * moveDir, 1);
			}
		}
	}

	function shiftList(delta, start, end) {
		if (delta !== 0) {
			adjusted = true;
		}

		for (var i = start; i < end; i++) {
			var item = list[i];
			var rect = item.rect;
			rect[xyDim] += delta;
			item.label[xyDim] += delta;
		}
	} // Squeeze gaps if the labels exceed margin.

	function squeezeGaps(delta, maxSqeezePercent) {
		var gaps = [];
		var totalGaps = 0;

		for (var i = 1; i < len; i++) {
			var prevItemRect = list[i - 1].rect;
			var gap = Math.max(list[i].rect[xyDim] - prevItemRect[xyDim] - prevItemRect[sizeDim], 0);
			gaps.push(gap);
			totalGaps += gap;
		}

		if (!totalGaps) {
			return;
		}

		var squeezePercent = Math.min(Math.abs(delta) / totalGaps, maxSqeezePercent);

		if (delta > 0) {
			for (var i = 0; i < len - 1; i++) {
				// Distribute the shift delta to all gaps.
				var movement = gaps[i] * squeezePercent; // Forward

				shiftList(movement, 0, i + 1);
			}
		} else {
			// Backward
			for (var i = len - 1; i > 0; i--) {
				// Distribute the shift delta to all gaps.
				var movement = gaps[i - 1] * squeezePercent;
				shiftList(-movement, i, len);
			}
		}
	}
	/**
   * Squeeze to allow overlap if there is no more space available.
   * Let other overlapping strategy like hideOverlap do the job instead of keep exceeding the bounds.
   */

	function squeezeWhenBailout(delta) {
		var dir = delta < 0 ? -1 : 1;
		delta = Math.abs(delta);
		var moveForEachLabel = Math.ceil(delta / (len - 1));

		for (var i = 0; i < len - 1; i++) {
			if (dir > 0) {
				// Forward
				shiftList(moveForEachLabel, 0, i + 1);
			} else {
				// Backward
				shiftList(-moveForEachLabel, len - i - 1, len);
			}

			delta -= moveForEachLabel;

			if (delta <= 0) {
				return;
			}
		}
	}

	return adjusted;
}
/**
 * Adjust labels on x direction to avoid overlap.
 */

export function shiftLayoutOnX(list, leftBound, rightBound, // If average the shifts on all labels and add them to 0
// TODO: Not sure if should enable it.
// Pros: The angle of lines will distribute more equally
// Cons: In some layout. It may not what user wanted. like in pie. the label of last sector is usually changed unexpectedly.
	balanceShift) {
	return shiftLayout(list, 'x', 'width', leftBound, rightBound, balanceShift);
}
/**
 * Adjust labels on y direction to avoid overlap.
 */

export function shiftLayoutOnY(list, topBound, bottomBound, // If average the shifts on all labels and add them to 0
	balanceShift) {
	return shiftLayout(list, 'y', 'height', topBound, bottomBound, balanceShift);
}
export function hideOverlap(labelList) {
	var displayedLabels = []; // TODO, render overflow visible first, put in the displayedLabels.

	labelList.sort(function (a, b) {
		return b.priority - a.priority;
	});
	var globalRect = new BoundingRect(0, 0, 0, 0);

	function hideEl(el) {
		if (!el.ignore) {
			// Show on emphasis.
			var emphasisState = el.ensureState('emphasis');

			if (emphasisState.ignore == null) {
				emphasisState.ignore = false;
			}
		}

		el.ignore = true;
	}

	for (var i = 0; i < labelList.length; i++) {
		var labelItem = labelList[i];
		var isAxisAligned = labelItem.axisAligned;
		var localRect = labelItem.localRect;
		var transform = labelItem.transform;
		var label = labelItem.label;
		var labelLine = labelItem.labelLine;
		globalRect.copy(labelItem.rect); // Add a threshold because layout may be aligned precisely.

		globalRect.width -= 0.1;
		globalRect.height -= 0.1;
		globalRect.x += 0.05;
		globalRect.y += 0.05;
		var obb = labelItem.obb;
		var overlapped = false;

		for (var j = 0; j < displayedLabels.length; j++) {
			var existsTextCfg = displayedLabels[j]; // Fast rejection.

			if (!globalRect.intersect(existsTextCfg.rect)) {
				continue;
			}

			if (isAxisAligned && existsTextCfg.axisAligned) {
				// Is overlapped
				overlapped = true;
				break;
			}

			if (!existsTextCfg.obb) {
				// If self is not axis aligned. But other is.
				existsTextCfg.obb = new OrientedBoundingRect(existsTextCfg.localRect, existsTextCfg.transform);
			}

			if (!obb) {
				// If self is axis aligned. But other is not.
				obb = new OrientedBoundingRect(localRect, transform);
			}

			if (obb.intersect(existsTextCfg.obb)) {
				overlapped = true;
				break;
			}
		} // TODO Callback to determine if this overlap should be handled?

		if (overlapped) {
			hideEl(label);
			labelLine && hideEl(labelLine);
		} else {
			label.attr('ignore', labelItem.defaultAttr.ignore);
			labelLine && labelLine.attr('ignore', labelItem.defaultAttr.labelGuideIgnore);
			displayedLabels.push(labelItem);
		}
	}
}